package at.ac.tuwien.lsds.schedule.strategy;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;

import org.apache.log4j.Logger;

import at.ac.tuwien.lsds.exception.MachineException;
import at.ac.tuwien.lsds.exception.SchedulerException;
import at.ac.tuwien.lsds.federation.CloudPartner;
import at.ac.tuwien.lsds.model.Application;
import at.ac.tuwien.lsds.model.PhysicalMachine;
import at.ac.tuwien.lsds.model.PhysicalMachineImpl;
import at.ac.tuwien.lsds.util.SchedulerOutputFile;
import at.ac.tuwien.lsds.util.Settings;

/**
 * These scheduling algorithm creates for each application a new vm on a physical machine
 */
public class SchedulerA extends ScheduleStrategyImpl{
	private static final Logger log = Logger.getLogger(SchedulerA.class);
	
	public SchedulerA(){
		pms = new ArrayList<PhysicalMachine>();
		queue = new ArrayList<Application>();
		
//		SchedulerOutputFile.getInstance().setScenario(Settings.getScenario().toString());
		SchedulerOutputFile.getInstance().setScheduler("A");
	}
	
	@Override
	public void runApplication(Application app) throws SchedulerException{
		Boolean isScheduled = schedule(app);
		
		if(!isScheduled){
			queue.add(app);
		}
	}

	@Override
	public void runApplication(Application app, Boolean queueing) throws SchedulerException{
		Boolean isScheduled = schedule(app);
		
		if(!isScheduled){
			if(queueing){
				queue.add(app);
			} else {
				throw new SchedulerException("cannot schedule the Application. Not enough resources");	
			}
		}
	}

	@Override
	public void update(Observable o, Object arg) {
		PhysicalMachine pm = (PhysicalMachine)o;
		if(0 == pm.getRunningVMCount()){
			pm.shutdown();
			pms.remove(pm);
			
			log.debug("Shutdown a Physical Machine - PM Count:" + pms.size());
		}
		
		scheduleQueue();
	}
	
	private boolean schedule(Application app){
		if(0 < Settings.getPmCount() && pms.size() == 0){
			PhysicalMachine pm = new PhysicalMachineImpl();
			((PhysicalMachineImpl)pm).addObserver(this);
			
			totalPms++;
			
			pms.add(pm);
		}
		
		Iterator<PhysicalMachine> iterator = pms.iterator();
		Boolean isApplied = false;
		while(iterator.hasNext()){
			PhysicalMachine pm = iterator.next();
			
			int freeHdd = pm.getHdd() - pm.getHddInUse();
			int freeRam = pm.getRam() - pm.getRamInUse();
			int freeCpu = pm.getCpu() - pm.getCpuInUse();
			
			if(freeHdd >= app.getHdd() && freeRam >= app.getRam() && freeCpu >= app.getCpu()){
				
				try {
					pm.startVM(app.getHdd(), app.getRam(), app.getCpu());
					pm.addApplication(app);
					
//					totalDuration += app.getDuration();
//					totalPowerConsumption += pm.getPowerConsumtion();
					
					totalVms++;
					
					isApplied = true;
					break;
				} catch (MachineException e) {
					log.info(e.getMessage());
				}
			}
		}
		
		if(isApplied){
//			addLine(app, false);
			return true;
		}
		
		if(Settings.getPmCount() > pms.size()){
			PhysicalMachine pm = new PhysicalMachineImpl();
			((PhysicalMachineImpl)pm).addObserver(this);
			pms.add(pm);
			
			totalPms++;
			
			try {
				pm.startVM(app.getHdd(), app.getRam(), app.getCpu());
				pm.addApplication(app);
				
//				totalDuration += app.getDuration();
				totalVms++;
//				totalPowerConsumption += pm.getPowerConsumtion();
				
				isApplied = true;
			} catch (MachineException e) {
				log.info(e.getMessage());
			}
		}
		
		if(isApplied){
//			addLine(app, false);
			return true;
		}
		
		CloudPartner cp = new CloudPartner();
		for(int i = 0; i < Settings.getCloudPartnerCount(); i++){
			if(cp.sendApplication(app)){
//				addLine(app, true);
				
//				totalDuration += app.getDuration();
				totalOutSourced++;
				
				return true;
			}
		}
		
		return false;
	}

	private void scheduleQueue(){
		Iterator<Application> iterator = queue.iterator();
		List<Application> rem = new ArrayList<Application>();
		while(iterator.hasNext()){
			Application app = iterator.next();
			
			if(schedule(app)){
				rem.add(app);
			}
		}
		
		queue.removeAll(rem);
	}
	
//	private void addLine(Application app, boolean outSourced){
//		int totalHdd = 0;
//		int totalRam = 0;
//		int totalCpu = 0;
//		int runningVms = 0;
//		float totalPowerConsumption = 0;
//		int os = outSourced? 1 : 0;
//		
//		Iterator<PhysicalMachine> iterator = pms.iterator();
//		while(iterator.hasNext()){
//			PhysicalMachine pm = iterator.next();
//			
//			totalHdd += pm.getHddInUse();
//			totalRam += pm.getRamInUse();
//			totalCpu += pm.getCpuInUse();
//			runningVms += pm.getRunningVMCount();
//			totalPowerConsumption += pm.getPowerConsumtion();
//		}
//		
//		SchedulerOutputFile sof = SchedulerOutputFile.getInstance();
//		SchedulerOutputRow sor = new SchedulerOutputRow(app.getTimestamp(), totalRam, totalCpu, 
//				totalHdd, pms.size(), runningVms, totalPowerConsumption, 0, os);
//		
//		sof.addRow(sor);
//	}
}
